/*
 * Matt Adams
 * TCSS 558 HW1
 * Fall 2013
 */
package server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.SortedSet;
import java.util.StringTokenizer;

import tools.Log;

/**
 * Handles TCP server functionality for spelling client-server program.
 * 
 * @author Matt Adams
 */
public class TCPSpellingServer extends SpellingServer {
    
    //Static variable for server.
    private static final String OK = "OK";
    private static final String NO = "NO";
    private static final String INVALID = "INVALID";
    private static final String BYE = "GOODBYE";
    
    /**
     * Server socket.
     */
    private ServerSocket serverSocket;
    /**
     * Client socket.
     */
    private Socket clientSocket;
    /**
     * Reader to receive input.
     */
    private BufferedReader bufIn;
    /**
     * Writer to send output.
     */
    private BufferedWriter bufOut;
    
    /**
     * Constructs TCPSpellingServer object.
     * @param portFilenameArray Listing of port and filename.
     */
    public TCPSpellingServer(String[] portFilenameArray) {       
        super(portFilenameArray);
        setupSocket();

    }
    
    /**
     * Initializes server and waits to accept a connection.
     */
    public void initializeServer() {
        try {
            this.clientSocket = this.serverSocket.accept();
            Log.out("    Accepted connection from <" + clientSocket.getInetAddress().getHostAddress() + ">:<" + clientSocket.getPort() + ">");
            bufIn = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
            bufOut = new BufferedWriter(new OutputStreamWriter(this.clientSocket.getOutputStream()));
        } catch (IOException e) {
            Log.err("Failed to setup server for receiving data: " + e);
            errorClose();
        }
    }
    
    /**
     * Handles receiving data from a client and sending a response to the same client.
     * @throws IOException 
     */
    protected void receiveAndSend() throws IOException {

        String queryLine = null;
        while (queryLine == null) {
            queryLine = bufIn.readLine();
        }
        if (queryLine.isEmpty()) {
            this.bufOut.write(BYE + "\r\n");
            this.bufOut.flush();
            Log.out("Queries complete. Closing local client socket.");
            this.clientSocket.close();
            initializeServer();
        } else {
            final StringTokenizer  tokenizer = new StringTokenizer(queryLine);
            if (tokenizer.countTokens() != 2) {
                Log.err("Malformed query, has incorrect number of tokens: " + tokenizer.countTokens());
                sendInvalidResponse();
                return;
            }
            final int queryNumber = parseQueryNumber(tokenizer.nextToken());
            if (queryNumber < 0) {
                return;
            }
            final String word = tokenizer.nextToken();

            int numberOfWords = 0;
            SortedSet<String> closeWords = null;
            if (this.wordList.isInList(word)) {
                numberOfWords = -1;
            } else {
                closeWords = this.wordList.getCloseWords(word);
                numberOfWords = closeWords.size();
            }
            final String closeWordString = this.displayQueryResponse(word, numberOfWords, closeWords);

            if (closeWordString != null) {
                this.bufOut.write(queryNumber + " " + NO + " " + closeWordString + "\r\n");
            } else if (numberOfWords == -1) {
                this.bufOut.write(queryNumber + " " + OK + "\r\n");
            } else {
                this.bufOut.write(queryNumber + " " + NO + "\r\n");
            }
            this.bufOut.flush();
        }
    }
    
    /**
     * Returns integer from string.
     * 
     * @param queryNumber String representation of a number.
     * @return Return integer from string.
     */
    private int parseQueryNumber(final String queryNumber) {
        int result = 0;
        try {
            result = Integer.parseInt(queryNumber);
            if (result < 0) {
                Log.err("Malformed query, number cannot be less than 0: " + queryNumber);
                sendInvalidResponse();
            }
        } catch(NumberFormatException e) {
            Log.err("Malformed query, not valid integer before query word");
            result = -1;
            sendInvalidResponse();
        }

        return result;
    }
    
    /**
     * Sends invalid response to client.
     */
    private void sendInvalidResponse() {
        Log.out("Connection closed.");
        try {
            this.bufOut.write(INVALID + "\r\n");
            this.bufOut.flush();
        } catch (IOException e) {
            Log.err("Error sending INVALID respone: " + e);
        }
    }
    
    /**
     * Sets up socket.
     */
    private void setupSocket() {
        try {            
            this.serverSocket = new ServerSocket(this.port);
            Log.out("Socket created with port: " + this.serverSocket.getLocalPort());
        } catch (IOException e) {
            Log.err("Error creating socket for udp spelling server: " + e.getMessage());
            errorClose();
        }
    }
    
    /**
     * Main to run server.
     * @param portFilenameArray Listing of port and filename.
     */
    public static void main(String[] portFilenameArray) {
        final TCPSpellingServer tcpSpellingServer = new TCPSpellingServer(portFilenameArray);
        tcpSpellingServer.initializeServer();
        tcpSpellingServer.runServer();
    }

}
